前面幾章開始使用 Debugger 動態分析 Malware,但 Malware 作者當然也知道分析人員會使用 Debugger。
因此 Malware 可能加入 Anti-Debugging 技術,用來:
Anti-Debugging 的方法非常多,本章主要介紹實務上常見的幾類技巧,以及分析時如何辨認與繞過。
【聲明】由於時間問題本日文章由 AI 生成QQ
最直接的 Anti-Debugging 方法就是:
檢查目前 Process 是否有 Debugger Attached。
Malware 可以透過 Windows API、PEB 等記憶體結構,甚至系統中留下的 Debugger 痕跡進行判斷。
最基本的 API:
IsDebuggerPresent();
它會檢查 Process Environment Block(PEB)中的 Debugger 狀態。
概念很簡單:
0 → 沒有 Debugger
nonzero → 有 Debugger
因此如果在 Malware Imports 或 Assembly 中看到 IsDebuggerPresent,就應該立刻懷疑 Anti-Debugging。
功能與 IsDebuggerPresent 類似。
雖然名稱有 Remote,但它不是檢查遠端電腦,而是可以檢查本機指定 Process 是否被 Debug。
這是 ntdll.dll 中的 Native API。
Malware 可以指定:
ProcessDebugPort = 0x7
查詢 Process 是否正在被 Debug。
如果沒有 Debugger:
DebugPort = 0
否則會得到 Debug Port。
OutputDebugString 原本是把字串送給 Debugger 顯示,但也能反過來偵測 Debugger。
書中的做法大致是:
SetLastError()
↓
OutputDebugString()
↓
GetLastError()
↓
比較 Error Value
根據 Error Value 是否改變,判斷 Debugger 是否存在。
這類 API-based Anti-Debugging 最簡單的繞過方法通常是:
修改 API 呼叫後的 Flag、Return Value 或 Conditional Jump,強迫 Malware 走「沒有 Debugger」的路徑。
直接呼叫 IsDebuggerPresent 太明顯,因此 Malware 也可以:
自己去讀 Windows 的資料結構。
其中最重要的就是:
PEB(Process Environment Block)
在 32-bit Windows Process 中,PEB 可以透過:
fs:[30h]
取得。
PEB 裡面有一個:
BeingDebugged
位於 PEB Offset:
+0x02
因此可能看到:
mov eax, fs:[30h]
mov ebx, [eax+2]
test ebx, ebx
概念就是:
fs:[30h]
↓
PEB
↓
BeingDebugged
↓
是否有 Debugger
繞過方式很直接:
把 BeingDebugged 改成 0
修改 Flag
強迫 Conditional Jump 走 No Debugger 路線
所以看到:
fs:[30h]
時要特別注意,它是很典型的 Anti-Debugging 特徵。
Debugger 啟動 Process 時,Heap 的設定也可能不同。
PEB Offset:
0x18 → ProcessHeap
Heap Header 中又包含:
Flags
ForceFlags
Malware 可以檢查這些 Flag,判斷 Process 是否由 Debugger 啟動。
例如:
mov eax, fs:[30h]
mov eax, [eax+18h]
cmp [eax+10h], 0
jne DebuggerDetected
要注意這些 Offset 會隨 Windows 版本而不同,因此不是看到某個固定 Offset 就一定代表 Anti-Debugging。
PEB 中還有另一個常被檢查的欄位:
NTGlobalFlag
教材中的 32-bit 範例位於:
PEB + 0x68
當 Process 從 Debugger 啟動時,某些 Heap Debugging Flags 會被開啟,常見組合值為:
0x70
因此 Malware 可能:
mov eax, fs:[30h]
cmp [eax+68h], 70h
jz DebuggerDetected
繞過方式同樣可以修改 Flag,或直接修改後面的 Branch。
Malware 不一定要直接檢查 Process,也可以尋找分析環境留下的痕跡。
例如檢查 Registry:
HKLM\SOFTWARE\Microsoft\
Windows NT\CurrentVersion\AeDebug
也可能搜尋:
Debugger executable
Debugger process
Debugger window
例如:
FindWindow("OLLYDBG", 0);
如果找到名稱為 OLLYDBG 的 Window,就知道 OllyDbg 正在執行。
因此 Malware Analysis 環境本身也可能成為被偵測的依據。
前面的技巧是在找「Debugger 是否存在」。
另一種思路則是:
檢查程式有沒有出現 Debugging 才會產生的行為。
主要包含:
INT Scanning
Code Checksum
Timing Checks
Software Breakpoint 通常是 Debugger 把原本的 Instruction 暫時替換成:
INT 3
Opcode = 0xCC
因此 Malware 可以掃描自己的 Code:
搜尋 0xCC
↓
找到
↓
可能有人設 Software Breakpoint
教材範例就是取得自己的 Code Address 後掃描 0xCC。
一種繞過方式是:
改用 Hardware Breakpoint。
因為 Hardware Breakpoint 不需要把原本的 Code 改成 0xCC。
Malware 也可以計算自己 Code 的:
CRC
MD5
如果分析人員設置 Software Breakpoint 或 Patch Code,原始 Bytes 改變,Checksum 就會不同。
概念:
Calculate checksum
↓
Compare expected checksum
↓
不同 → Code 被修改
分析時如果看到程式大量遍歷自己的 Code,最後又拿結果與固定值比較,就要考慮是不是 Anti-Debugging Checksum。
Timing Check 是非常常見的 Anti-Debugging。
原因很簡單:
正常執行:
Instruction → Instruction → Instruction
Debug 時:
Instruction
↓
Breakpoint
↓
分析人員看半天
↓
Single-step
執行時間自然差很多。
Malware 因此可以:
取得 Time A
↓
執行一些 Code
↓
取得 Time B
↓
B - A
↓
太久 → Debugger
最典型的 Timing Check 是:
rdtsc
它會取得 CPU Time Stamp Counter,結果放入:
EDX:EAX
Malware 執行兩次 rdtsc:
RDTSC
↓
執行 Code
↓
RDTSC
↓
計算差值
如果差距太大,就推測正在被 Debug。
Windows API 也可以做到類似效果:
QueryPerformanceCounter
GetTickCount
例如:
a = GetTickCount();
MaliciousActivityFunction();
b = GetTickCount();
if ((b-a) > threshold)
// Debugger Detected
繞過 Timing Check 最簡單的方法之一,是不要在兩次計時之間慢慢 Single-step。
可以直接 Run 過 Timing Check,在後面設 Breakpoint;或直接修改比較結果與 Conditional Jump。
前面是在「發現 Debugger」,接下來則是:
直接讓 Debugger 不好用。
本章介紹:
TLS Callbacks
Exceptions
Interrupts
Debugger Vulnerabilities
一般使用 Debugger 時,很容易認為:
Entry Point = 第一個執行的 Instruction
但這不一定正確。
Windows 支援:
TLS Callback
TLS Callback 可以在程式的正常 Entry Point 之前執行。
因此 Malware 可以:
Program Load
↓
TLS Callback
↓
Anti-Debugging / Malicious Code
↓
Entry Point
如果分析人員只在 Entry Point 開始 Debug,就可能已經漏掉一段重要 Code。
PE 中可能看到:
.tls
在 IDA Pro 中則可以使用:
CTRL-E
查看 Entry Points,其中也包含 TLS Callback。教材第 10 頁的 Figure 16-1、16-2 就分別展示了 PEview 中的 TLS Table,以及 IDA 的 TLS Callback Entry Point。
Exception 也可以拿來判斷 Debugger。
正常情況:
Exception
↓
Program Exception Handler
但 Debugger 存在時通常會先攔截 Exception:
Exception
↓
Debugger
↓
Program
因此 Malware 可以故意製造 Exception,再觀察自己的 Handler 是否正常收到。
分析 Malware 時,如果 Debugger 一直停在各種 Exception,不一定代表程式壞掉,也可能是 Anti-Debugging。
教材建議分析時讓 Debugger 將 Exception 傳回程式處理。
因為:
INT 3 = 0xCC
本來就是 Software Breakpoint 使用的 Interrupt,Malware 可以自己插入 INT 3,故意讓 Debugger 停下。
更特殊的是:
CD 03
也可以產生 INT 3。
教材指出,這種 2-byte INT 3 在特定舊版 Debugger 中可能讓 EIP 處理錯誤,使 Debugger 中的執行流程與正常執行不同。
INT 2D 與 Kernel Debugger 有關,也可以利用 Debugger 對 Interrupt 的處理差異進行 Anti-Debugging。
另一個比較特殊的是:
ICEBP
Opcode = F1
它會產生 Single-step Exception。
如果程式正在被 Single-step,Debugger 可能把它當成一般 Single-step Exception,導致 Malware 原本設定的 Exception Handler 沒有執行。
因此教材給出的重點很直接:
遇到
icebp時,不要 Single-step 過去。
最後一類方法不是「偵測 Debugger」,而是:
直接利用 Debugger 本身的 Bug。
本章以舊版 OllyDbg 為主要例子。
例如 PE Optional Header 中:
NumberOfRvaAndSizes
正常 DataDirectory 數量是:
0x10
Windows Loader 對超過這個範圍的值有自己的處理方式,但舊版 OllyDbg 的處理不同。
因此 Malware 可以故意設:
NumberOfRvaAndSizes = 0x99
讓 Malware 正常執行,但 OllyDbg 載入時出錯。
教材第 14 頁的 Figure 16-5 就是在說明這個差異。
另一個例子是:
SizeOfRawData
Malware 可以故意設定極大的值,例如:
77777777h
使舊版 OllyDbg Crash。
這種情況可以手動修正 PE Header,或改用不受影響的 Debugger。
舊版 OllyDbg 還存在 OutputDebugString 相關問題。
Malware 可以傳入特殊 Format String,例如大量:
%s%s%s%s...
使 Debugger Crash。
所以看到非常異常的 OutputDebugString 呼叫,也可能不是普通 Debug Message,而是 Anti-Debugging。
Chapter 16 的 Anti-Debugging 可以整理成四個核心方向:
| 類型 | 主要概念 |
|---|---|
| Debugger Detection | API、PEB、BeingDebugged、ProcessHeap、NTGlobalFlag |
| Debugger Behavior | INT Scanning、Checksum、Timing Check |
| Debugger Interference | TLS Callback、Exception、INT 3、INT 2D、ICEBP |
| Debugger Vulnerability | 利用 PE Header 或 Debugger 本身的 Bug |
實際分析 Malware 時,如果程式莫名其妙在 Conditional Jump 後結束、Debugger 中和正常執行結果不同,或頻繁看到 fs:[30h]、Debugger Detection API、RDTSC、GetTickCount、Exception 等行為,都應該考慮 Anti-Debugging。
而繞過方法其實有一個共同核心:
先找出 Malware 用什麼資訊判斷 Debugger,再讓這個判斷得到「沒有 Debugger」的結果。
可能是修改 Flag、Return Value、PEB、Conditional Jump,避開 Timing Check,改用 Hardware Breakpoint,或調整 Debugger 對 Exception 的處理。
Anti-Debugging 技巧很多,不太可能全部背下來。更重要的是建立辨識能力:當程式在 Debugger 裡出現不合理行為時,先懷疑 Malware 是否正在觀察或干擾 Debugger,而不是直接認為分析失敗。 這也是本章最後強調的重點。
